#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <netdb.h>

#define SERVER "pc12921.mathematik.uni-marburg.de"
#define SERVER_PORT 2342
#define FILE_NAME "udp_client_output"

#define SHOW_DBG
#undef SHOW_DBG

#define USE_BASE64_DECODER
//#undef USE_BASE64_DECODER

#ifdef SHOW_DBG
#define DBG_MSG(...) fprintf(stderr,__VA_ARGS__)
#else
#define DBG_MSG(...)
#endif


void error(char*);
void send_ack(int,struct sockaddr *,int,int);
void send_getfile(int,struct sockaddr *);
int main(int,char**);

#ifdef USE_BASE64_DECODER
struct base64decoder{
  unsigned char charcnt;
  unsigned char buffer;
};
unsigned char base64SymbolTable(unsigned char in);
void base64decode(struct base64decoder* decoder,unsigned char* input,int size,FILE* stream);
#endif

void error(char* msg){
  perror(msg);
  exit(1);
}


int main(int argc, char** argv){
  int sock;
  struct hostent *serveraddress;
  struct sockaddr_in server;
  struct sockaddr_in local;
  
  sock = socket(AF_INET, SOCK_DGRAM, 0);
  if(sock <0){
    error("creating socket\n");
  }
  
  
  memset(&server, 0, sizeof(server));
  server.sin_family = AF_INET;
  server.sin_port = htons(SERVER_PORT);
  
  serveraddress = gethostbyname(SERVER);
  
  if(serveraddress == NULL){
    error("resolving hostname\n");
  }
  
  bcopy(
    serveraddress->h_addr,
    &(server.sin_addr.s_addr),
    serveraddress->h_length
  );
  
  memset(&local, 0, sizeof(local));
  local.sin_family = AF_INET;
  local.sin_addr.s_addr = INADDR_ANY;
  
  struct timeval timeout;
  memset(&timeout,0,sizeof(timeout));
  timeout.tv_sec=1;
  
  if(setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO, (void *)&timeout,sizeof(timeout)) < 0){
    error("setting timeout\n");
  }
  
  if(bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0){
    error("binding socket\n");
  }
  
  DBG_MSG("server: %s:%d\n",SERVER,SERVER_PORT);
  
  send_getfile(sock,(struct sockaddr *)&server);
  int buffersize = (1<<16)+1;
  char buffer[buffersize];
  bzero(buffer,buffersize);
  socklen_t server_len = sizeof(server);

  FILE *file;
  file = fopen(FILE_NAME,"w");
  if(file == NULL){
    error("opening file");
  }
  
  int running =1;
  int last_bytes=0;
  int next_seqnr=0;
  
#ifdef USE_BASE64_DECODER
  struct base64decoder decoder = {0,0};
#endif
  
  while(running){
    int n;
    DBG_MSG("-----------------------------------------------------\n");
    DBG_MSG("waiting for datagram %d\n",next_seqnr);
    if((n = recvfrom(sock,buffer,buffersize-1,0,NULL,NULL) )<0){
      DBG_MSG("receive timed out\n");
      //resend last ack
      if(next_seqnr==0){
	send_getfile(sock,(struct sockaddr *)&server);
      }else{
	send_ack(sock,(struct sockaddr *)&server,next_seqnr,last_bytes);
      }
    }else{
      DBG_MSG("received datagram\n");
      if(buffer[0]=='#'){
	  //zero terminated string (strtol & fputs)
	buffer[n]='\0';
      
	char* start_length;
	long int recv_seqnr = strtol(buffer+1,&start_length,10);
	start_length++;
      
	char* start_data;
	long int recv_bytes = strtol(start_length,&start_data,10);
	start_data++;
	DBG_MSG("seqnr %d\n",recv_seqnr);
	DBG_MSG("length %d byte\n",recv_bytes);
	if(recv_seqnr == next_seqnr){
	  DBG_MSG("correct seqnr\n");
	  if(recv_bytes==0){
	    running=0;
	    next_seqnr++;
	    last_bytes=recv_bytes;
	  }else if( buffer+n == start_data+recv_bytes){
	    DBG_MSG("datagram okay\n");
	    next_seqnr++;
	    last_bytes=recv_bytes;
#ifdef USE_BASE64_DECODER
	    base64decode(&decoder,start_data,recv_bytes,file);
#else
	    fputs(start_data,file);
#endif
	  }else{
	    DBG_MSG("datagram had wrong size\n");
	  }
	}else{
	  DBG_MSG("wrong seqnr\n");
	}
      
      }else{
	DBG_MSG("wrong format\n");
      }
      
      // send next ack or resend last ack
      if(next_seqnr==0){
	send_getfile(sock,(struct sockaddr *)&server);
      }else{
	send_ack(sock,(struct sockaddr *)&server,next_seqnr,last_bytes);
      }
    }
  }
  
  fclose(file);
  close(sock);
  return EXIT_SUCCESS;
}

void send_getfile(int sock,struct sockaddr * server){
  char* getcmd = "GETFILE";
  DBG_MSG("sending GETFILE\n");
  if(sendto(sock, getcmd, strlen(getcmd), 0, server, sizeof(*server)) < 0){
    error("send\n");
  }
}

void send_ack(int sock,struct sockaddr * server,int seqnr,int bytes){
  char buf[100];
  sprintf(buf,"#%d %d",seqnr,bytes);
  DBG_MSG("sending ack %d %d\n",seqnr,bytes);
  if(sendto(sock, buf, strlen(buf), 0, server, sizeof(*server)) < 0){
    error("sending\n");
  }
}

#ifdef USE_BASE64_DECODER
void base64decode(struct base64decoder* decoder,unsigned char* input,int size, FILE* stream){
  int i;
  for(i=0;i<size;i++){
    if(input[i] == '='){
      decoder->charcnt=0;
      decoder->buffer='\0';
      break;
    }
    if(input[i] == '\n' | input[i] == '\r'){
      continue;
    }
    unsigned char decoded = base64SymbolTable(input[i]);
    switch(decoder->charcnt){
      case 0:
	decoder->buffer = decoded<<2;
	decoder->charcnt = 1;
	break;
      case 1:
	decoder->buffer |= decoded >>4;
	putc(decoder->buffer,stream);
	decoder->buffer = decoded <<4;
	decoder->charcnt = 2;
	break;
      case 2:
	decoder->buffer |= decoded >>2;
	putc(decoder->buffer,stream);
	decoder->buffer = decoded <<6;
	decoder->charcnt = 3;
	break;
      case 3:
	decoder->buffer |= decoded;
	putc(decoder->buffer,stream);
	decoder->charcnt = 0;
    }
  }
}

unsigned char base64SymbolTable(unsigned char in){
  if(in >= 'A' && in <= 'Z'){
    return in - 'A';
  }
  if(in >= 'a' && in <= 'z'){
    return in - 'a' +26;
  }
  if(in >= '0' && in <= '9'){
    return in - '0' + 52;
  }
  if(in == '+'){
    return 62;
  }
  if(in == '/'){
    return 63;
  }
  error("base64 unknown symbol\n");
  return 0;
}
#endif